/*
  QMine - Version 1.0
  Author - Xs.JIONG
  Created At 2019.8.16
  Copyrights(c) 2019 XsJIONG
  All Rights Reserved.
*/

const char *_WON=				"Yeah!!!!!! You Won! Time:%d";
const char *_DIE=				"Boom!!!!!! You Died! Time:%d";
const char *_OFFLINE_MSG=		"Choose Difficulty\nEasy\nMedium\nHard\nChaos\nCustom";
const char *_MAINSCREEN_TITLE=	"QMine";
const char *_STATE=				"%d %ds";
const char *_WELCOME=			"Welcome to QMine!\nMade By Xs.JIONG\nUsing Dev-Cpp";
const char *_OFFLINE=			"Offline";
const char *_ONLINE=			"Online";
const char *_GOTO_PREV=			"[ESC]";
const char *_INPUT_WIDTH=		"Width:";
const char *_INPUT_HEIGHT=		"Height:";
const char *_INPUT_MINE_COUNT=	"Mine Count:";
const char *_SOCKET_ERROR=		"SocketError:%d\n";
const char *_INIT_FAIL=			"Failed to Initialize";
const char *_ONLINE_IP=			"Online IP";
const char *_REFRESH=			"[Refresh]";
const char *_JOIN_GAME=			"Join";
const char *_CREATE_GAME=		"Create";
const char *_BIND_SOCKET_ERR=	"Failed to Bind Socket";
const char *_ERR_GET_IP=		"Failed to Get IP";
const char *_DISCONNECTED=		"Disconnected";
const char *_CONNECTING=		"Connecting";
const char *_CONNECTED=			"Conncted";
const char *_IP=				"IP:%s";
const char *_WAITING_CONNECTION="Waiting for Connection";
const char *_SENDING_MAP=		"Sending Map";
const char *_RECEIVING_MAP=		"Receiving Map";
const char *_DONE=				"Done";
const char *_GAME_STARTING=		"Game Starting";
const char *_OP_FLAG=			"Opponent's Flag:%d";
const char *_OP_WON=			"Opponent has won! You lost!";
const char *_OP_LOST=			"Opponent has lost! You won!";
const char *_OP_EXIT=			"Opponent has exited! You won!";

const char OP_INC_FLAG=			1;
const char OP_DEC_FLAG=			2;
const char OP_QUIT=				3;
const char OP_OPPO_WON=			4;
const char OP_OPPO_LOST=		5;
const char OP_OPPO_EXIT=		6;

#include <cstdio>
#include <ctime>
#include <conio.h>
#include <windows.h>
#include <vector>
#include <string>

#define WAIT_TIME 4

#define MIN_BLANK_COUNT 10

#define MIN_WIDTH 5
#define MIN_HEIGHT 5
#define MAX_WIDTH 80
#define MAX_HEIGHT 50

#define WINDOW_WIDTH 27
#define WINDOW_HEIGHT 17

#define BUFFER_SIZE 1024

const int FR_WHITE=	FOREGROUND_INTENSITY|FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE;
const int BG_WHITE=	BACKGROUND_INTENSITY|BACKGROUND_RED|BACKGROUND_GREEN|BACKGROUND_BLUE;

// Following 5 describe how the object should be printed
// Struct:(CHAR<<8)|COLOR
const int C_EDGE=	(4<<8)		|BG_WHITE;
const int C_EMPTY=	(' '<<8)	|0;
const int C_LAND=	(' '<<8)	|BG_WHITE;
const int C_FLAG=	('+'<<8)	|BG_WHITE;
const int C_MINE=	(15<<8)		|BACKGROUND_INTENSITY|BACKGROUND_RED|FR_WHITE;
const int C_GMINE=	(15<<8)		|BG_WHITE;

#define SCALE 3 //  As SCALE get higher, the density of mine will get lower
#define LIM 5 // The max of the radius which ensures there's no mine surrounding the first type position

#define MINE 9
#define MASK_FLAG 32
#define MASK_LAND 16
#define MASK 15
#define adis(a,b) (((a)>(b))?((a)-(b)):((b)-(a)))
#define isMine(x) (((x)&MINE)==MINE)
#define isFlag(x) (((x)&MASK_FLAG)==MASK_FLAG)
#define isLand(x) (((x)&MASK_LAND)==MASK_LAND)

#define KillThread(c) {WaitForSingleObject(c,INFINITE);CloseHandle(c);}
#define C(c) (isFlag(c)?C_FLAG:(isLand(c)?C_LAND:(c?(c+'0'):C_EMPTY)))
#define update(x,y) (setCursor(y-1,x-1),PrintObject(C(q[x][y])))

extern void setCursor(int,int);
template <class...T>
extern void printC(const char* f, T...arg);
inline void WaitThread(HANDLE c) {
	WaitForSingleObject(c,INFINITE);
	CloseHandle(c);
}
namespace Network {
	const int BROADCAST_PORT = 10235;
	const int TCP_PORT = 13456;
	char Buffer[BUFFER_SIZE];
	SOCKET UDP_SOCKET,TCP_SOCKET,TCP_SERVER;
	SOCKADDR_IN UDP_SIN,TCP_SIN;
	int SOCK_LEN;
	HANDLE BROADCAST_THREAD,SEARCHIP_THREAD;
	bool UDP_RUNNING,TCP_RUNNING;
	WSAData WData;
	std::vector<std::string> SEARCHED_IP;
	bool ABORT=0;
	char *IP;
	inline void SocketError() {
		printf(_SOCKET_ERROR,WSAGetLastError());
	}
	BOOL CtrlHandler(DWORD fdwCtrlType) {
		switch (fdwCtrlType) {
			case CTRL_BREAK_EVENT:
			case CTRL_CLOSE_EVENT:
			case CTRL_LOGOFF_EVENT:
			case CTRL_SHUTDOWN_EVENT:
				ABORT=1;
				UDP_RUNNING=false;
				if (TCP_SERVER) closesocket(TCP_SERVER);
				if (TCP_SOCKET) closesocket(TCP_SOCKET);
				if (UDP_SOCKET) closesocket(UDP_SOCKET);
				WSACleanup();
				exit(0);
				return TRUE;
		}
		return FALSE;
	}
	bool Initialize() {
		bool ret=WSAStartup(MAKEWORD(2,2),&WData);
		if (ret) return 1;
		SOCK_LEN=sizeof(SOCKADDR);
		SetConsoleCtrlHandler((PHANDLER_ROUTINE)CtrlHandler, TRUE);
		return 0;
	}
	inline void showAll() {
		static int i,j;
		for (i=4;i<WINDOW_HEIGHT-1;i++)
		for (j=1,setCursor(1,i);j<WINDOW_WIDTH-1;j++) putchar(' ');
		j=SEARCHED_IP.size();
		if (WINDOW_HEIGHT-6<j) j=WINDOW_HEIGHT-6;
		for (int i=0;i<j;i++)
			setCursor(1,i+4),printC(SEARCHED_IP[i].c_str());
	}
	inline int getClickedIP(int x, int y) {
		if (x==0||x==WINDOW_WIDTH-1) return -1;
		if (y<4) return -1;
		y-=4;
		static int len,st;
		if (y>=SEARCHED_IP.size()) return -1;
		len=SEARCHED_IP[y].length();
		st=(WINDOW_WIDTH-len)>>1;
		if (x>=st&&x<st+len) return y;
		return -1;
	}
	DWORD WINAPI _GetInputAndSend(LPVOID param) {
		do {
			gets(Buffer);
			if (send(TCP_SOCKET,Buffer,strlen(Buffer),0)==-1) return 0;
		} while (TCP_RUNNING);
	}
	DWORD WINAPI _BroadcastIP(LPVOID param) {
		int iplen=strlen(IP);
		while (UDP_RUNNING) {
			int ret=sendto(UDP_SOCKET, IP, iplen, 0, (sockaddr*)&UDP_SIN, sizeof(UDP_SIN));
			Sleep(2000);
		}
		if (!ABORT) closesocket(UDP_SOCKET);
	}
	DWORD WINAPI _SearchIP(LPVOID param) {
		static int ret;
		std::string ss;
		bool rr=0;
		while (UDP_RUNNING) {
			memset(Buffer,0,sizeof(char)*strlen(Buffer));
			ret=recvfrom(UDP_SOCKET, Buffer, sizeof(Buffer), 0, (sockaddr*)&UDP_SIN, &SOCK_LEN);
			if (ABORT) break;
			if (!UDP_RUNNING) break;
			if (ret==-1) {
				SocketError();
				rr=1;
				break;
			}
			if (!ret) {
				puts(_DISCONNECTED);
				rr=1;
				break;
			}
			Buffer[ret]='\0';
			ss=Buffer;
			bool find=false;
			for (int i=0;i<SEARCHED_IP.size();i++)
				if (SEARCHED_IP[i]==ss) {
					find=true;
					break;
				}
			if (!find) {
				SEARCHED_IP.push_back(ss);
				showAll();
			}
		}
		SEARCHIP_THREAD=NULL;
		return rr;
	}
	bool BroadcastIP() {
		UDP_SOCKET=socket(AF_INET,SOCK_DGRAM,0);
		bool broad=true;
		setsockopt(UDP_SOCKET, SOL_SOCKET, SO_BROADCAST, (char*)&broad, sizeof(BOOL));
		if (UDP_SOCKET==INVALID_SOCKET) {
			SocketError();
			WSACleanup();
			return 1;
		}
		ZeroMemory(Buffer,BUFFER_SIZE);
		UDP_SIN.sin_addr.S_un.S_addr=INADDR_BROADCAST;
		UDP_SIN.sin_family=AF_INET;
		UDP_SIN.sin_port=htons(BROADCAST_PORT);
		static char local[256];
		if (gethostname(local, sizeof(local))==SOCKET_ERROR) {
			puts(_ERR_GET_IP);
			return 1;
		}
		HOSTENT* host=gethostbyname(local);
		if (host==NULL) {
			puts(_ERR_GET_IP);
			return 1;
		}
		in_addr addr;
		memcpy(&addr, host->h_addr_list[0], sizeof(in_addr));
		IP=inet_ntoa(addr);
		UDP_RUNNING=1;
		BROADCAST_THREAD=CreateThread(NULL, 0, _BroadcastIP, NULL, 0, 0);
		return 0;
	}
	bool WaitTCP() {
		TCP_SOCKET=socket(AF_INET,SOCK_STREAM,0);
		bool rr=true;
		setsockopt(TCP_SOCKET, SOL_SOCKET, SO_REUSEADDR, (char*)&rr, sizeof(BOOL));
		if (TCP_SOCKET==INVALID_SOCKET) {
			SocketError();
			WSACleanup();
			return 1;
		}
		TCP_SIN.sin_addr.S_un.S_addr=INADDR_ANY;
		TCP_SIN.sin_family=AF_INET;
		TCP_SIN.sin_port=htons(TCP_PORT);
		bind(TCP_SOCKET,(SOCKADDR*)&TCP_SIN,sizeof(SOCKADDR));
		listen(TCP_SOCKET,1);
		TCP_SERVER=accept(TCP_SOCKET,(SOCKADDR*)&TCP_SIN,&SOCK_LEN);
		if (ABORT) return 0;
		UDP_RUNNING=false;
		closesocket(UDP_SOCKET);
		if (BROADCAST_THREAD) WaitThread(BROADCAST_THREAD);
		return 0;
	}
	bool SearchIP() {
		UDP_SOCKET=socket(AF_INET,SOCK_DGRAM,0);
		bool rr=true;
		setsockopt(UDP_SOCKET, SOL_SOCKET, SO_REUSEADDR, (char*)&rr, sizeof(BOOL));
		if (UDP_SOCKET==INVALID_SOCKET) {
			SocketError();
			WSACleanup();
			return 1;
		}
		ZeroMemory(Buffer,BUFFER_SIZE);
		UDP_SIN.sin_addr.S_un.S_addr = INADDR_ANY;
		UDP_SIN.sin_family = AF_INET;
		UDP_SIN.sin_port = htons(BROADCAST_PORT);
		if (bind(UDP_SOCKET,(SOCKADDR*)&UDP_SIN,sizeof(SOCKADDR))==SOCKET_ERROR) {
			puts(_BIND_SOCKET_ERR);
			return 1;
		}
		UDP_RUNNING=1;
		showAll();
		if (!SEARCHIP_THREAD) SEARCHIP_THREAD=CreateThread(NULL, 0, _SearchIP, NULL, 0, 0);
		return 0;
	}
	bool ConnectTo(const char *ip) {
		UDP_RUNNING=0;
		closesocket(UDP_SOCKET);
		if (SEARCHIP_THREAD) WaitThread(SEARCHIP_THREAD);
		TCP_SOCKET=socket(AF_INET,SOCK_STREAM,0);
		TCP_SIN.sin_addr.S_un.S_addr=inet_addr(ip);
		TCP_SIN.sin_family=AF_INET;
		TCP_SIN.sin_port=htons(TCP_PORT);
		connect(TCP_SOCKET,(SOCKADDR*)&TCP_SIN,SOCK_LEN);
		if (ABORT) return 0;

		TCP_RUNNING=1;
	}
	bool CheckAlive() {
		while (TCP_RUNNING) {
			int ret=send(TCP_SOCKET,"\0",1,0);
			if (ret==-1) {
				puts(_DISCONNECTED);
				TCP_RUNNING=0;
				closesocket(TCP_SOCKET);
				WSACleanup();
				return 0;
			}
			Sleep(2000);
		}
	}
}
//============================================


SOCKET *USING_SOCKET;
bool ONLINE;
int MX=0,MY=0;
int ESC_LEN=strlen(_GOTO_PREV);
int WIDTH_MIN=0;
int WIDTH,HEIGHT,MINE_COUNT;
char **q=NULL;
bool NotGen;
HANDLE HIN,HOUT,RECV_THREAD;
bool Playing,OPEnd;
char OPState;
int RFlag,OPRFlag,Seconds,MsgLen;
enum MouseKey {
    NONE,LEFT,RIGHT,DCLICK
};
struct MouseEvent {
	MouseKey type;
	int x,y;
};
MouseEvent E;
template<typename T>
T min(const T &a, const T &b) {
	return (a<b)?a:b;
}
inline void PrintObject(int x) {
	if (!(x>>8)) {
		putchar(x);
		return;
	}
	SetConsoleTextAttribute(HOUT,x&255);
	putchar((unsigned char)(x>>8));
	SetConsoleTextAttribute(HOUT,FR_WHITE);
}
inline void PrintObject(int x, int t) {
	if (!(x>>8)) {
		while (t--) putchar(x);
		return;
	}
	SetConsoleTextAttribute(HOUT,x&255);
	char c=(unsigned char)(x>>8);
	while (t--) putchar(c);
	SetConsoleTextAttribute(HOUT,FR_WHITE);
}
inline void SetSize(int x, int y) {
	if (x<WIDTH_MIN) x=WIDTH_MIN;
	static CONSOLE_SCREEN_BUFFER_INFO bufferInfo;
	GetConsoleScreenBufferInfo(HOUT, &bufferInfo);
	SMALL_RECT& winInfo = bufferInfo.srWindow;
	static COORD ws;
	static SMALL_RECT info;
	ws.X=winInfo.Right-winInfo.Left+1, ws.Y=winInfo.Bottom-winInfo.Top+1;
	if (ws.X>x||ws.Y>y) {
		info.Left=info.Top=0;
		info.Right=(x<ws.X?x:ws.X)-1;
		info.Bottom=(y<ws.Y?y:ws.Y)-1;
		SetConsoleWindowInfo(HOUT,TRUE,&info);
	}
	static COORD size;
	size.X=x,size.Y=y;
	SetConsoleScreenBufferSize(HOUT, size);
	info.Left=info.Top=0;
	info.Right=x-1,info.Bottom=y-1;
	SetConsoleWindowInfo(HOUT, TRUE, &info);
}
inline void ReleaseMap() {
	if (q) {
		for (int i=0;i<=HEIGHT;i++)
			delete[] q[i];
		delete[] q;
		q=NULL;
	}
}
inline void BuildMap() {
	register int i,j;
	q=new char*[HEIGHT+1];
	for (i=0;i<=HEIGHT;i++) {
		q[i]=new char[WIDTH+1];
		for (j=0;j<=WIDTH;j++) q[i][j]=MASK_LAND;
	}
}
inline COORD& getCursor() {
	static CONSOLE_SCREEN_BUFFER_INFO info;
	GetConsoleScreenBufferInfo(HOUT, &info);
	return info.dwCursorPosition;
}
inline int getCursorX() {
	static CONSOLE_SCREEN_BUFFER_INFO info;
	GetConsoleScreenBufferInfo(HOUT, &info);
	return info.dwCursorPosition.X;
}
inline int getCursorY() {
	static CONSOLE_SCREEN_BUFFER_INFO info;
	GetConsoleScreenBufferInfo(HOUT, &info);
	return info.dwCursorPosition.Y;
}
bool CURSOR_LOCK=0;
inline void setCursor(COORD& c) {
	SetConsoleCursorPosition(HOUT, c);
}
inline void setCursor(int x, int y) {
	static COORD c;
	c.X=x+MX,c.Y=y+MY;
	SetConsoleCursorPosition(HOUT, c);
}
inline void clearLine(int y) {
	setCursor(-MX,-MY+y);
	static CONSOLE_SCREEN_BUFFER_INFO bufferInfo;
	GetConsoleScreenBufferInfo(HOUT, &bufferInfo);
	COORD& size=bufferInfo.dwSize;
	for (int i=size.X;i;i--) putchar(' ');
}
void cls() {
	setCursor(0,0);
	static CONSOLE_SCREEN_BUFFER_INFO bufferInfo;
	GetConsoleScreenBufferInfo(HOUT, &bufferInfo);
	SMALL_RECT& size=bufferInfo.srWindow;
	for (int i=(size.Right-size.Left)*(size.Bottom-size.Top);i;i--) putchar(' ');
}
inline void GenerateMines(int cx, int cy) {
	register int e,i,j,x,y;
	int les=int((double)(WIDTH*HEIGHT-MINE_COUNT*SCALE));
	int ss=1;
	int lim=min(cx,min(cy,min(HEIGHT-cx+1,WIDTH-cy+1)));
	while ((((ss<<1)-2)*ss+1)<=les&&ss<=lim&&ss<=LIM) ss++;
	ss--;
	if (!ss) ss=2;
	les=WIDTH*HEIGHT-((ss<<1)-2)*ss-1;

	for (int w=0;w<MINE_COUNT;w++,les--) {
		e=int((double)rand()/RAND_MAX*les);
		if (e==les) e--;
		i=j=1;
		if (e)
			for (i=1;i<=HEIGHT;i++)
			for (j=1;j<=WIDTH;j++)
				if ((adis(i,cx)+adis(j,cy))>=ss&&q[i][j]!=MINE)
					if (!(--e)) goto fall;
		fall:;
		if (q[i][j]==MINE) {
			w--;
			continue;
		}
		q[i][j]=MINE;
		for (x=-1;x<=1;x++)
		for (y=-1;y<=1;y++)
			if ((i+x>=1)&&(i+x<=HEIGHT)&&(j+y>=1)&&(j+y<=WIDTH))
				if (q[i+x][j+y]!=MINE) q[i+x][j+y]++;
	}
	for (x=1;x<=HEIGHT;x++)
	for (y=1;y<=WIDTH;y++)
		q[x][y]|=MASK_LAND;
}
inline void updateState() {
	while (CURSOR_LOCK);
	CURSOR_LOCK=1;
	COORD& c=getCursor();
	setCursor(-1,HEIGHT);
	for (int i=-1;i<=WIDTH;i++) putchar(' ');
	setCursor(-1,HEIGHT);
	printf(_STATE,RFlag,Seconds);
	setCursor(c);
	CURSOR_LOCK=0;
}
DWORD WINAPI CountingThread(LPVOID param) {
	int d;
	while (Playing) {
		Sleep(1000);
		if (!Playing) break;
		if (Seconds<d) break;
		d=++Seconds;
		updateState();
	}
	return 0;
}
inline void InitConsole() {
	HIN=GetStdHandle(STD_INPUT_HANDLE);
	HOUT=GetStdHandle(STD_OUTPUT_HANDLE);
}
MouseKey __LAST_EVENT=NONE;
inline void getMouseEvent() {
#define leftClicked(msg) (msg.Event.MouseEvent.dwButtonState==FROM_LEFT_1ST_BUTTON_PRESSED)
#define rightClicked(msg) (msg.Event.MouseEvent.dwButtonState==RIGHTMOST_BUTTON_PRESSED)
	static INPUT_RECORD msg;
	static DWORD pointer;
	fall:;
	do ReadConsoleInput(HIN, &msg, 1, &pointer);
	while (!(msg.EventType==MOUSE_EVENT&&
	        (leftClicked(msg)
	         ||rightClicked(msg)
	         ||msg.Event.MouseEvent.dwEventFlags==DOUBLE_CLICK
	         ||msg.Event.MouseEvent.dwEventFlags==MOUSE_MOVED)));
	E.type = (msg.Event.MouseEvent.dwEventFlags==DOUBLE_CLICK?DCLICK:(leftClicked(msg)?LEFT:(rightClicked(msg)?RIGHT:NONE)));
	if (E.type==__LAST_EVENT&&E.type!=NONE) goto fall;
	__LAST_EVENT=E.type;
	COORD &t=msg.Event.MouseEvent.dwMousePosition;
	E.x=t.X-MX;
	E.y=t.Y-MY;
#undef leftClicked
#undef rightClicked
}
inline void pause() {
	while (1) {
		getMouseEvent();
		setCursor(E.x,E.y);
		if (E.type==LEFT) return;
	}
}
inline void ShowAll() {
	setCursor(0,0);
	register int x,y,i;
	for (x=1;x<=HEIGHT;x++)
	for (y=1,setCursor(0,x-1);y<=WIDTH;y++)
		PrintObject(isMine(q[x][y])?C_GMINE:C_EMPTY);
}
inline void clearMsg() {
	setCursor(-MX, HEIGHT+2);
	for (int i=0;i<MsgLen;i++) putchar(' ');
}
inline void Over(const char* msg) {
	ShowAll();
	Playing=0;
	clearMsg();
	setCursor(-MX, HEIGHT+2);
	static char DUMP[80];
	sprintf(DUMP,msg,Seconds);
	MsgLen=strlen(DUMP);
	puts(DUMP);
}
inline void OutputMap() {
	setCursor(0,0);
	register int x,y;
	for (x=1;x<=HEIGHT;x++)
	for (y=1,setCursor(0,x-1);y<=WIDTH;y++)
		PrintObject(C(q[x][y]));
}
#define Try if (x>=1&&y>=1&&x<=HEIGHT&&y<=WIDTH) if ((!isFlag(q[x][y]))&&(!isMine(q[x][y]))&&isLand(q[x][y])) Spread(x,y)
#define FuckinTry {x++;Try;y++;Try;x--;Try;x--;Try;y--;Try;y--;Try;x++;Try;x++;Try;x--;y++;}
void Spread(int x, int y) {
	q[x][y]&=~MASK_LAND;
	update(x,y);
	if (q[x][y]) return;
	FuckinTry;
}
inline void onLeft(int x, int y) {
	if (q[x][y]==MINE) return;
	if (!isLand(q[x][y])) return;
	if (isFlag(q[x][y])) return;
	if (isMine(q[x][y])) {
		Over(_DIE);
		return;
	}
	Try;
	setCursor(y-1,x-1);
}
inline void onRight(int x, int y) {
	if (q[x][y]==MINE) return;
	if (!isLand(q[x][y])) return;
	if (isFlag(q[x][y])) {
		q[x][y]&=~MASK_FLAG,RFlag++;
		if (ONLINE)
			Network::Buffer[0]=OP_INC_FLAG,
			send(*USING_SOCKET,Network::Buffer,1,0);
	} else {
		q[x][y]|=MASK_FLAG,RFlag--;
		if (ONLINE)
			Network::Buffer[0]=OP_DEC_FLAG,
			send(*USING_SOCKET,Network::Buffer,1,0);
	}
	updateState();
	update(x,y);
	setCursor(y-1,x-1);
}
inline void onDouble(int x, int y) {
	update(x,y);
	if (q[x][y]==MINE) return;
	if (isLand(q[x][y])) return;
	if (isFlag(q[x][y])) return;
	if (!q[x][y]) return;
	register int i,j;
	int a;
	char c;
	int ex=0;
	for (i=-1;i<=1;i++) {
		if (x+i>HEIGHT||x+i<1) continue;
		for (j=-1;j<=1;j++) {
			if ((!i)&&(!j)) continue;
			if (y+j>WIDTH||y+j<1) continue;
			c=q[x+i][y+j];
			if (isFlag(c)) {
				c&=~MASK_FLAG;
				if (c==(MINE|MASK_LAND)) ex++;
				else {
					Over(_DIE);
					return;
				}
			}
		}
	}
	if (ex==q[x][y]) {
		FuckinTry;
	}
	setCursor(y-1,x-1);
}
inline bool CheckAll() {
	register int i,j;
	for (i=1;i<=HEIGHT;i++)
	for (j=1;j<=WIDTH;j++)
		if ((!isMine(q[i][j]))&&isLand(q[i][j])) return false;
	return true;
}
#undef Try
#undef FuckinTry
inline void DrawBounds(int width, int height) {
	register int i,j;
	setCursor(0,0),PrintObject(C_EDGE,width);
	for (i=1;i<height-1;i++) setCursor(0,i),PrintObject(C_EDGE),setCursor(width-1,i),PrintObject(C_EDGE);
	setCursor(0,height-1),PrintObject(C_EDGE,width);
}
inline void updateOPFlag() {
	int x=getCursorX(),y=getCursorY();
	while (CURSOR_LOCK);
	CURSOR_LOCK=1;
	setCursor(-MX,HEIGHT+2);
	printf(_OP_FLAG,OPRFlag);
	setCursor(x,y);
	CURSOR_LOCK=0;
}
bool PlayGame(int cx, int cy) {
	register int Q;
	OPRFlag=RFlag=MINE_COUNT;
	OPEnd=0;
	SetSize(WIDTH+4,HEIGHT+7);
	cls();
	DrawBounds(WIDTH+4,HEIGHT+4);
	setCursor(1,1);
	printf(_GOTO_PREV);
	MX=MY=2;
	Playing=1;
	NotGen=(cx==-1||cy==-1);
	Seconds=0;

	clearMsg();
	updateState();
	HANDLE c=NULL;
	OutputMap();
	if (cx!=-1&&cy!=-1) {
		onLeft(cx,cy);
		c=CreateThread(NULL,0,CountingThread,NULL,0,0);
	}
	if (CheckAll()) {
		Over(_WON);
		ReleaseMap();
		if (c!=NULL) KillThread(c);
		MX=MY=0;
		return 0;
	}
	if (ONLINE) updateOPFlag();
	while (Playing) {
		getMouseEvent();
		if (!Playing) break;
		while (CURSOR_LOCK);
		CURSOR_LOCK=1;
		setCursor(E.x,E.y);
		CURSOR_LOCK=0;
		if (E.type==LEFT) {
			int tx=E.x+MX,ty=E.y+MY;
			if (tx>=1&&tx<=ESC_LEN&&ty==1) {
				Playing=0;
				if (ONLINE) {
					Network::Buffer[0]=OP_OPPO_EXIT;
					send(*USING_SOCKET,Network::Buffer,1,0);
				}
				ReleaseMap();
				if (c!=NULL) KillThread(c);
				MX=MY=0;
				return 1;
			}
		}
		Q=E.x,E.x=E.y,E.y=Q;
		E.x++,E.y++;
		if (E.x<1||E.x>HEIGHT||E.y<1||E.y>WIDTH) continue;

		if (E.type==NONE) continue;
		if (NotGen) {
			if (E.type!=LEFT) continue;
			GenerateMines(E.x,E.y);
			c=CreateThread(NULL,0,CountingThread,NULL,0,0);
			NotGen=0;
		}
		if (E.type==LEFT)
			onLeft(E.x,E.y);
		else if (E.type==RIGHT)
			onRight(E.x,E.y);
		else if (E.type==DCLICK)
			onDouble(E.x,E.y);
		if (CheckAll()) {
			Over(_WON);
			if (ONLINE) {
				Network::Buffer[0]=OP_OPPO_WON;
				send(*USING_SOCKET,Network::Buffer,1,0);
			}
			ReleaseMap();
			if (c!=NULL) KillThread(c);
			MX=MY=0;
			return 0;
		}
	}
	if (ONLINE&&OPEnd) {
		Over(OPState==2?_OP_EXIT:(OPState?_OP_WON:_OP_LOST));
		ReleaseMap();
		if (c!=NULL) KillThread(c);
		MX=MY=0;
		return 0;
	}
	if (ONLINE) {
		Network::Buffer[0]=OP_OPPO_LOST;
		send(*USING_SOCKET,Network::Buffer,1,0);
	}
	ReleaseMap();
	if (c!=NULL) KillThread(c);
	MX=MY=0;
	return 0;
}
static char _Tmp[1024];
template <class...T>
void printC(const char* f, T...arg) {
	sprintf(_Tmp,f,arg...);
	setCursor((WINDOW_WIDTH-strlen(_Tmp))>>1,getCursorY());
	puts(_Tmp);
}
template <class...T>
void printCM(const char* f, T...arg) {
	sprintf(_Tmp,f,arg...);
	int st=0,i;
	while (1) {
		for (i=st;_Tmp[i]!='\n'&&_Tmp[i]!='\0';i++);
		setCursor((WINDOW_WIDTH-i+st)>>1,getCursorY());
		if (_Tmp[i]=='\0') {
			puts(_Tmp+st);
			break;
		}
		_Tmp[i]='\0';
		puts(_Tmp+st);
		st=i+1;
	}
}
inline bool MainScreen() {
	cls();
	WIDTH_MIN=0;
	SetSize(WINDOW_WIDTH,WINDOW_HEIGHT+1);
	WIDTH_MIN=50;
	register int i,j,k,q;
	j=strlen(_WELCOME);
	k=1;
	for (i=0;i<j;i++) if (_WELCOME[i]=='\n') k++;
	setCursor(0,k);
	printCM(_WELCOME);
	setCursor(0,WINDOW_HEIGHT>>1);
	PrintObject(C_EDGE,WINDOW_WIDTH);
	k=WINDOW_WIDTH>>1;
	for (i=WINDOW_HEIGHT>>1;i<WINDOW_HEIGHT;i++) setCursor(k,i),PrintObject(C_EDGE);

	k=(WINDOW_HEIGHT>>2)+(WINDOW_HEIGHT>>1);
	int c=WINDOW_WIDTH>>1;
	int sp=(c-strlen(_ONLINE)+1)>>1;
	setCursor(sp,k);
	printf(_ONLINE);

	sp=(c-strlen(_OFFLINE)+2)>>1;
	setCursor(sp+(WINDOW_WIDTH>>1),k);
	printf(_OFFLINE);

	DrawBounds(WINDOW_WIDTH,WINDOW_HEIGHT);
	c=(WINDOW_HEIGHT>>1)+1;
	int ct=(WINDOW_WIDTH>>1)+1;
	while (1) {
		getMouseEvent();
		setCursor(E.x++,E.y++);
		if (E.type!=LEFT) continue;
		if (E.y<=c||E.y>=WINDOW_HEIGHT) continue;
		if (E.x>1&&E.x<ct) return true;
		else if (E.x>ct&&E.x<WINDOW_WIDTH) return false;
	}
}
inline int ReadNum(int line, const char *d, int min, int max) {
	static int t,ret;
	static int len,tt;
	register int i;
	register char c;
	len=strlen(d);
	t=ret=0;
	#define refresh {setCursor(1,line);tt=(WINDOW_WIDTH-len-t)>>1;for(i=tt;i>1;i--)putchar(' ');printf("%s",d);if(t)printf("%d",ret);for(i=WINDOW_WIDTH-tt-len-t;i>1;i--)putchar(' ');setCursor(tt+len+t,line);}
	refresh;
	do {
		c=getch();
		if (c=='\r'||c=='\n') {
			if (t==0) continue;
			if (ret<min) continue;
			return ret;
		} else if (c==27) return -1;
		else if (c=='\b') {
			if (t==0) continue;
			t--;
			ret/=10;
			refresh;
		} else if (c>='0'&&c<='9') {
			t++;
			ret=(ret<<3)+(ret<<1)+c-'0';
			if (ret>max) {
				ret=max;
				t=0;
				while (ret) ret/=10,t++;
				ret=max;
			}
			refresh;
		}
	} while (1);
	#undef refresh
}
struct LevelInfo {
	int w,h,c;
};
inline bool SelectMap(LevelInfo &ret) {
	cls();
	WIDTH_MIN=0;
	SetSize(WINDOW_WIDTH,WINDOW_HEIGHT+1);
	WIDTH_MIN=50;
	static char c;
	register int i,j,k;
	j=strlen(_OFFLINE_MSG);
	k=1;
	int last=0;
	static int s[6];
	for (i=0;i<j;i++) if (_OFFLINE_MSG[i]=='\n') {
		s[(k++)-1]=i-last-1;
		last=i;
	}
	setCursor(0,0);
	s[k-1]=i-last-1;
	int st=((WINDOW_HEIGHT-k)>>1);
	setCursor(0,st);
	st+=2;
	printCM(_OFFLINE_MSG);
	setCursor(1,1);
	puts(_GOTO_PREV);
	DrawBounds(WINDOW_WIDTH,WINDOW_HEIGHT);
	while (1) {
		getMouseEvent();
		setCursor(E.x,E.y);
		if (E.type!=LEFT) continue;
		if (E.y==1&&E.x>0&&E.x<=ESC_LEN) return 1;
		E.x++,E.y++;
		if (E.y>=st&&E.y<=st+k-2) {
			int tmp=E.y-st+1;
			int st=(WINDOW_WIDTH-s[tmp])>>1;
			if (E.x<=st||E.x>st+s[tmp]) continue;
			switch (tmp) {
				case 1:ret.w=ret.h=9,ret.c=10;break;
				case 2:ret.w=ret.h=16,ret.c=40;break;
				case 3:ret.w=30,ret.h=16,ret.c=99;break;
				case 4:ret.w=80,ret.h=50,ret.c=500;break;
				case 5:{
					cls();
					DrawBounds(WINDOW_WIDTH,WINDOW_HEIGHT);
					int st=(WINDOW_HEIGHT-3)>>2;
					ret.w=ReadNum(st++,_INPUT_WIDTH,MIN_WIDTH,MAX_WIDTH);
					if (ret.w==-1) return 1;
					ret.h=ReadNum(st++,_INPUT_HEIGHT,MIN_HEIGHT,MAX_HEIGHT);
					if (ret.h==-1) return 1;
					ret.c=ReadNum(st,_INPUT_MINE_COUNT,0,ret.w*ret.h-MIN_BLANK_COUNT);
					if (ret.c==-1) return 1;
					break;
				}
			}
			break;
		}
	}
	return 0;
}
DWORD WINAPI _RecvMsg(LPVOID param) {
	static char c[10];
	while (ONLINE) {
		recv(*USING_SOCKET,c,1,0);
		switch (c[0]) {
			case OP_INC_FLAG:OPRFlag++,updateOPFlag();break;
			case OP_DEC_FLAG:OPRFlag--,updateOPFlag();break;
			case OP_QUIT:return 0;
			case OP_OPPO_WON:OPEnd=1;OPState=1;Playing=0;break;
			case OP_OPPO_LOST:OPEnd=1;OPState=0;Playing=0;break;
			case OP_OPPO_EXIT:OPEnd=1;OPState=2;Playing=0;break;
		}
	}
	return 0;
}
inline bool JoinGame() {
	register int i,j;
	cls();
	DrawBounds(WINDOW_WIDTH,WINDOW_HEIGHT);
	setCursor(0,1);
	printC(_ONLINE_IP);
	setCursor(1,2),puts(_GOTO_PREV);
	int ref=WINDOW_WIDTH-strlen(_REFRESH)-1;
	setCursor(ref,2),puts(_REFRESH);
	setCursor(0,3);
	PrintObject(C_EDGE,WINDOW_WIDTH);
	if (Network::Initialize()) {
		setCursor(0,1),printC(_INIT_FAIL);
		WSACleanup();
		pause();
		return 1;
	}
	if (Network::SearchIP()) {
		WSACleanup();
		pause();
		return 1;
	}
	int ind;
	while (1) {
		getMouseEvent();
		setCursor(E.x,E.y);
		if (E.type!=LEFT) continue;
		if (E.y==2) {
			if (E.x>0&&E.x<=ESC_LEN) {
				Network::UDP_RUNNING=0;
				closesocket(Network::UDP_SOCKET);
				if (Network::SEARCHIP_THREAD) WaitThread(Network::SEARCHIP_THREAD);
				return 1;
			}
			if (E.x>=ref&&E.x<WINDOW_WIDTH-1) {
				Network::SEARCHED_IP.clear();
				Network::showAll();
			}
			continue;
		}
		ind=Network::getClickedIP(E.x,E.y);
		if (ind==-1) continue;
		break;
	}
	cls();
	DrawBounds(WINDOW_WIDTH,WINDOW_HEIGHT);
	setCursor(1,1),puts(_CONNECTING),setCursor(1,2);
	Network::ConnectTo(Network::SEARCHED_IP[ind].c_str());
	puts(_CONNECTED),setCursor(1,3);
	char *buf=Network::Buffer;
	recv(Network::TCP_SOCKET,buf,20,0);
	WIDTH=*((int*)buf);
	HEIGHT=*((int*)(buf+4));
	MINE_COUNT=*((int*)(buf+8));
	int cx,cy;
	cx=*((int*)(buf+12));
	cy=*((int*)(buf+16));
	printf("%dx%d %d\n",WIDTH,HEIGHT,MINE_COUNT),setCursor(1,4);
	puts(_RECEIVING_MAP);
	ReleaseMap();
	BuildMap();
	for (i=1;i<=HEIGHT;i++) {
		recv(Network::TCP_SOCKET,q[i],WIDTH+1,0);
		for (j=1,setCursor(1,5);j<WIDTH-1;j++) putchar(' ');
		setCursor(1,5),printf("%.2f%%",(double)i/HEIGHT*100);
	}
	setCursor(1,6),puts(_DONE),setCursor(1,7);
	ONLINE=1;
	USING_SOCKET=&Network::TCP_SOCKET;
	RECV_THREAD=CreateThread(NULL,0,_RecvMsg,NULL,0,0);
	bool ret=PlayGame(cx,cy);
	ONLINE=0;
	buf[0]=OP_QUIT;
	send(*USING_SOCKET,buf,1,0);
	WaitThread(RECV_THREAD);
	closesocket(*USING_SOCKET);
	WSACleanup();
	if (!ret) pause();
	return 1;
}
inline bool CreateGame() {
	register int i,j;
	static LevelInfo info;
	if (SelectMap(info)) return 1;
	ReleaseMap();
	WIDTH=info.w,HEIGHT=info.h,MINE_COUNT=info.c;
	BuildMap();
	int cx=(rand()%info.h)+1,cy=(rand()%info.w)+1;
	GenerateMines(cx,cy);
	cls();
	DrawBounds(WINDOW_WIDTH,WINDOW_HEIGHT);
	if (Network::Initialize()) {
		setCursor(0,1),printC(_INIT_FAIL);
		WSACleanup();
		pause();
		return 1;
	}
	if (Network::BroadcastIP()) {
		WSACleanup();
		pause();
		return 1;
	}
	setCursor(1,1),printf(_IP,Network::IP),setCursor(1,2);
	puts(_WAITING_CONNECTION),setCursor(1,3);
	if (Network::WaitTCP()) {
		WSACleanup();
		pause();
		return 1;
	}
	puts(_CONNECTED),setCursor(1,4);
	char *buf=Network::Buffer;
	*((int*)buf)=WIDTH;
	*((int*)(buf+4))=HEIGHT;
	*((int*)(buf+8))=MINE_COUNT;
	*((int*)(buf+12))=cx;
	*((int*)(buf+16))=cy;
	send(Network::TCP_SERVER,buf,20,0);
	puts(_SENDING_MAP),setCursor(1,5);
	for (i=1;i<=HEIGHT;i++) {
		send(Network::TCP_SERVER,q[i],WIDTH+1,0);
		for (j=1,setCursor(1,5);j<WIDTH-1;j++) putchar(' ');
		setCursor(1,5),printf("%.2f%%",(double)i/HEIGHT*100);
	}
	setCursor(1,6),puts(_DONE),setCursor(1,7);
	ONLINE=1;
	USING_SOCKET=&Network::TCP_SERVER;
	RECV_THREAD=CreateThread(NULL,0,_RecvMsg,NULL,0,0);
	bool ret=PlayGame(cx,cy);
	ONLINE=0;
	buf[0]=OP_QUIT;
	send(*USING_SOCKET,buf,1,0);
	WaitThread(RECV_THREAD);
	closesocket(*USING_SOCKET);
	WSACleanup();
	if (!ret) pause();
	return 1;
}
inline bool Offline() {
	static LevelInfo ret;
	if (SelectMap(ret)) return 1;
	while (1) {
		WIDTH=ret.w,HEIGHT=ret.h,MINE_COUNT=ret.c;
		BuildMap();
		if (PlayGame(-1,-1)) return 1;
		while (1) {
			getMouseEvent();
			setCursor(E.x,E.y);
			if (E.type!=LEFT) continue;
			if (E.y==1&&E.x>0&&E.x<=ESC_LEN) return 1;
			else break;
		}
	}
	return 0;
}
inline bool Online() {
	register int i,j;
	cls();
	DrawBounds(WINDOW_WIDTH,WINDOW_HEIGHT);
	setCursor(0,WINDOW_HEIGHT>>1);
	PrintObject(C_EDGE,WINDOW_WIDTH);
	setCursor(0,WINDOW_HEIGHT>>2);
	printC(_ONLINE);
	setCursor(1,1);
	puts(_GOTO_PREV);
	j=WINDOW_WIDTH>>1;
	for (i=WINDOW_HEIGHT>>1;i<WINDOW_HEIGHT;i++) setCursor(j,i),PrintObject(C_EDGE);
	int t=(WINDOW_HEIGHT>>1)+(WINDOW_HEIGHT>>2);
	setCursor((j-strlen(_JOIN_GAME)>>1)+1,t),puts(_JOIN_GAME);
	setCursor((j-strlen(_CREATE_GAME)>>1)+j+1,t),puts(_CREATE_GAME);
	j=(WINDOW_HEIGHT>>1)+1;
	int ct=(WINDOW_WIDTH>>1)+1;
	while (1) {
		getMouseEvent();
		setCursor(E.x,E.y);
		if (E.type!=LEFT) continue;
		if (E.y==1&&E.x>0&&E.x<=ESC_LEN) return 1;
		E.x++,E.y++;
		if (E.y<=j||E.y>=WINDOW_HEIGHT) continue;
		if (E.x>1&&E.x<ct) {
			if (JoinGame()) return 1;
			return 0;
		} else if (E.x>ct&&E.x<WINDOW_WIDTH) {
			if (CreateGame()) return 1;
			return 0;
		}
	}
	return 0;
}

int main() {
	register int i,j;
	srand(time(0));
	SetConsoleTitle(_MAINSCREEN_TITLE);
	InitConsole();
	Main:;
	bool online=MainScreen();
	LevelInfo ret;

	if (online) {
		if (Online()) goto Main;
	} else {
		if (Offline()) goto Main;
	}
	return 0;
}
